package TableImage

import (
	"errors"
	"fmt"
	"github.com/fogleman/gg"
	"sync"
)

type TableImageClient struct {
	Conf   *TableImageConf //表格配置信息
	Dc     *gg.Context     //画布信息
	ImageW int             //画布宽
	ImageH int             //画布高
}

var onceApi sync.Once
var apiInterface TableImageApi

func NewTableImageClient() TableImageApi {
	onceApi.Do(func() {
		apiInterface = &TableImageClient{}
	})
	return apiInterface
}

/**
初始化表格配置
@param fontPath string [true] 指定字体文件路径
@param rows string [true] 多少行
@param columns string [true] 多少列
*/
func (this *TableImageClient) InitTableImage(fontPath string, rows, columns int) error {
	if rows == 0 || columns == 0 {
		return errors.New("无效的行或列配置")
	}
	initConf := &TableImageConf{
		Margin:         20,       //距离四周的边界值
		MarginTop:      55,       //距离顶部的边界值
		Padding:        10,       //每个单元格的内边距
		FontPointTitle: 20,       //标题字体占用的像素点
		FontPointDate:  16,       //日期字体占用的像素点
		OutLineWidth:   2,        //外边框线条宽度
		InLineWidth:    1,        //内边框线条宽度
		FontPath:       fontPath, //字体路径;这个必须从客服端来传
		Rows:           rows,     //多少行
		RowHight:       50,       //行高
		Columns:        columns,  //多少列
		ColumnWidth:    130,      //列宽
	}
	this.Conf = initConf
	return nil
}

/**
创建画布
@return  返回画布的信息
*/
func (this *TableImageClient) CreateImage() *TableImageClient {
	if this.Conf == nil {
		panic("无效参数配置，创建画布失败！")
	}
	//上下文，含长和宽
	var w = int(this.Conf.Margin*2) + this.Conf.Columns*int(this.Conf.ColumnWidth)
	var h = int(this.Conf.MarginTop+this.Conf.Margin) + this.Conf.Rows*int(this.Conf.RowHight)
	dc := gg.NewContext(w, h)
	//设置当前图片底色
	dc.SetRGB(1, 1, 0.745)
	dc.Clear() //清理一下上下文，下面开始画画
	this.ImageW = w
	this.ImageH = h
	this.Dc = dc
	return this
}

/**
设置图片标题
@param title string [true] 图片标题
@param mark string [true] 图片备注，一般写日期
*/
func (this *TableImageClient) SetTitle(title, mark string) *TableImageClient {
	if this.Dc == nil {
		panic("画布无效，不能设置标题")
	}
	//设置图片标题及日期显示
	//设置标题文字颜色
	this.Dc.SetRGB(0, 0, 0)
	this.loadTTF(this.Conf.FontPath, this.Conf.FontPointTitle) //加载字体及字体大小

	tWidth, _ := this.Dc.MeasureString(title) // 测算字符串将在画布中占用的宽与长
	this.Dc.DrawStringWrapped(title, 0, 0, -float64(this.ImageW)+tWidth, -1, 0.5, 1, gg.AlignLeft)
	this.loadTTF(this.Conf.FontPath, this.Conf.FontPointDate)

	dateY := this.Conf.MarginTop - 5
	this.Dc.DrawString(mark, this.Conf.Margin, dateY)
	return this
}

/**
生成表格
@param dataTitle []TitleParse [true] 表格设置
@param data []map[string]interface{} [true] 表格数据
*/
func (this *TableImageClient) CreateTable(dataTitle []TitleParse, data []map[string]interface{}) *TableImageClient {
	if len(dataTitle) == 0 {
		panic("无效的表头设置")
	}
	//设置线条颜色及透明度
	this.Dc.SetRGBA(0, 0, 0, 1)
	//先画行线条
	RowOverX := this.Conf.Margin + float64(this.Conf.Columns)*this.Conf.ColumnWidth
	ColumnOverY := this.Conf.MarginTop + float64(this.Conf.Rows)*this.Conf.RowHight

	//画列的线条
	for i := 0; i <= this.Conf.Columns; i++ {
		if i == 0 || i == this.Conf.Columns { //第一条和最后一条线加粗
			this.Dc.SetLineWidth(this.Conf.OutLineWidth) //设置外行线条宽度
		} else {
			this.Dc.SetLineWidth(this.Conf.InLineWidth) //设置内行线条宽度
		}
		RowX := float64(i) * this.Conf.ColumnWidth
		this.Dc.DrawLine(this.Conf.Margin+RowX, this.Conf.MarginTop, this.Conf.Margin+RowX, ColumnOverY) //从左上角向右画
		//没有这句是不会把线最终画出来的
		this.Dc.Stroke()
	}
	//画行的线条
	for i := 0; i <= this.Conf.Rows; i++ {
		if i == 0 || i == this.Conf.Rows { //第一条和最后一条线加粗
			this.Dc.SetLineWidth(this.Conf.OutLineWidth) //设置外行线条宽度
		} else {
			this.Dc.SetLineWidth(this.Conf.InLineWidth) //设置内行线条宽度
		}
		ColumnY := float64(i) * this.Conf.RowHight
		this.Dc.DrawLine(this.Conf.Margin, this.Conf.MarginTop+ColumnY, RowOverX, this.Conf.MarginTop+ColumnY) //从左上角的位置开始向下画
		//没有这句是不会把线最终画出来的000
		this.Dc.Stroke()
		//写字
		childNum := len(data)
		if i <= childNum { //这里需要画边界线，所有就会多出一个边界值
			index := 0 //写字的遍历个数
			for _, n := range dataTitle {
				if index >= this.Conf.Columns {
					break
				}
				fontX := this.Conf.Margin + float64(index)*this.Conf.ColumnWidth
				fontY := this.Conf.MarginTop + this.Conf.RowHight*float64(i)
				ay := -float64(this.Conf.RowHight/8) * 0.25
				if i == 0 { //第一行写标题
					sWidth, _ := this.Dc.MeasureString(n.Name) // 测算字符串将在画布中占用的宽与长
					ax := -float64(this.Conf.ColumnWidth - sWidth)
					this.Dc.DrawStringWrapped(n.Name, fontX, fontY, ax, ay, 0.5, 1, gg.AlignLeft)
				} else { //后面的写入具体的数据
					itemData := data[i-1]
					txt := itemData[n.Field]
					setVal := fmt.Sprintf("%v", txt)
					sWidth, _ := this.Dc.MeasureString(setVal) // 测算字符串将在画布中占用的宽与长
					if sWidth > this.Conf.ColumnWidth {
						//1个像素=1bite,1个字节=1B,因为1b=8B,所以1个像素=8字节
						cutLen := int(this.Conf.ColumnWidth/8) - (this.Conf.Padding*2)/8
						setVal = setVal[0:cutLen]
						setVal = setVal + "..."
						//sWidth, _ = this.Dc.MeasureString(setVal)
					}
					//sWidth = sWidth + float64(this.Conf.Padding)
					this.Dc.DrawStringWrapped(setVal, fontX, fontY, -float64(this.Conf.Padding), ay, 0.5, 1, gg.AlignLeft)
				}
				index++
			}
		}
	}
	return this
}

/**
保存图片
@param path string [true] 保存的路径
*/
func (this *TableImageClient) SaveImage(path string) {
	this.Dc.SavePNG(path) //保存上下文为一张图片
	this.resetParam()
}

/**
销毁参数
*/
func (this *TableImageClient) resetParam() {
	this = &TableImageClient{
		Conf:   nil,
		Dc:     nil,
		ImageW: 0,
		ImageH: 0,
	}
}

/**
加载字体文件
@mark 可以不穿字体文件，使用系统默认的，如果传了，就必须是正确的路径
*/
func (this *TableImageClient) loadTTF(fontPath string, fontPoint float64) {
	if err := this.Dc.LoadFontFace(fontPath, fontPoint); err != nil {
		panic(err)
	}
}
